package server

import "C"
import (
	"JudgeClient/compiler"
	"JudgeClient/global"
	"JudgeClient/idl"
	"JudgeClient/service"
	"encoding/json"
	"errors"
	"github.com/nsqio/go-nsq"
	"os"
	"path/filepath"
	"strconv"
)

type JServer struct {
	C          *nsq.Consumer
	lookupAddr []string
	P          *nsq.Producer
}

func InitJServer() *JServer {
	conf := nsq.NewConfig()
	con, err := nsq.NewConsumer(global.Conf.Server.NsqConTopic, global.Conf.Server.NsqChannel, conf)
	pro, err := nsq.NewProducer(global.Conf.Server.NsqAddr, conf)
	if err != nil {
		global.Logger.Errorf("nsq start err:%v", err)
		return nil
	}
	err = pro.Ping()
	if err != nil {
		// 关闭生产者
		pro.Stop()
		pro = nil
	}
	C := &JServer{
		C:          con,
		P:          pro,
		lookupAddr: global.Conf.Server.NsqLookUpAddr,
	}

	return C
}

func (c *JServer) DoConsume() {
	defer func() {
		c.C.Stop()
	}()
	c.C.AddHandler(nsq.HandlerFunc(c.Judge))
	err := c.C.ConnectToNSQLookupds(c.lookupAddr)
	if err != nil {
		global.Logger.Errorf("can not find nsq!,err:%v", err)
	}
	<-c.C.StopChan
}

func (c *JServer) Judge(message *nsq.Message) error {
	var input idl.JMessage
	var err error

	err = json.Unmarshal(message.Body, &input)
	if err != nil {
		global.Logger.Errorf("body unmarshal failed")
	}

	// generate submission id.

	submissionId := input.Sid

	// init submission dir

	var submissionDir string

	if submissionDir, err = initSubmissionEnv(submissionId); err != nil {
		//c.JSON(http.StatusOK, gin.H{
		//	"code": -1,
		//	"data": err.Error(),
		//})
		return err
	}

	defer freeSubmissionEnv(submissionDir)

	var conf global.LanguageCompileConfig

	DefaultEnv := []string{
		"LANG=en_US.UTF-8",
		"LANGUAGE=en_US:en",
		"LC_ALL=en_US.UTF-8",
	}
	switch input.Language {
	case "c":
		conf = global.LanguageCompileConfig{
			CompileConfig: global.CompileConfig{
				SrcName:        "main.c",
				ExeName:        "main",
				MaxCpuTime:     global.Conf.Judge.MaxCpuTime,
				MaxRealTime:    global.Conf.Judge.MaxRealTime,
				MaxMemory:      global.Conf.Judge.Unlimited,
				CompileCommand: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {src_path} -lm -o {exe_path}",
			},
			RunConfig: global.RunConfig{
				Command:     "{exe_path}",
				SeccompRule: "c_cpp",
				Env:         DefaultEnv,
			},
		}
		break
	case "cpp":
		conf = global.LanguageCompileConfig{
			CompileConfig: global.CompileConfig{
				SrcName:        "main.cpp",
				ExeName:        "main",
				MaxCpuTime:     global.Conf.Judge.MaxCpuTime,
				MaxRealTime:    global.Conf.Judge.MaxRealTime,
				MaxMemory:      global.Conf.Judge.Unlimited,
				CompileCommand: "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++11 {src_path} -lm -o {exe_path}",
			},
			RunConfig: global.RunConfig{
				Command:     "{exe_path}",
				SeccompRule: "c_cpp",
				Env:         DefaultEnv,
			},
		}
		break
	case "java":
		conf = global.LanguageCompileConfig{
			CompileConfig: global.CompileConfig{
				SrcName:        "Main.java",
				ExeName:        "Main",
				MaxCpuTime:     global.Conf.Judge.MaxCpuTime,
				MaxRealTime:    global.Conf.Judge.MaxRealTime,
				MaxMemory:      global.Conf.Judge.Unlimited,
				CompileCommand: "/usr/bin/javac {src_path} -d {exe_dir} -encoding UTF8",
			},
			RunConfig: global.RunConfig{
				Command:     "/usr/bin/java -cp {exe_dir} -Xss1M -XX:MaxPermSize=16M -XX:PermSize=8M -Xms16M -Xmx{max_memory}k -Djava.security.manager -Dfile.encoding=UTF-8 -Djava.security.policy==/etc/java_policy -Djava.awt.headless=true Main",
				SeccompRule: "none",
				Env:         append(DefaultEnv, "MALLOC_ARENA_MAX=1"),
			},
		}
		break
	case "py2":
		conf = global.LanguageCompileConfig{
			CompileConfig: global.CompileConfig{
				SrcName:        "solution.py",
				ExeName:        "solution.pyc",
				MaxCpuTime:     global.Conf.Judge.MaxCpuTime,
				MaxRealTime:    global.Conf.Judge.MaxRealTime,
				MaxMemory:      global.Conf.Judge.Unlimited,
				CompileCommand: "/usr/bin/python -m py_compile {src_path}",
			},
			RunConfig: global.RunConfig{
				Command:     "/usr/bin/python {exe_path}",
				SeccompRule: "none",
				Env:         DefaultEnv,
			},
		}
		break
	default:
		err = errors.New("invalid language support")
		global.Logger.Error(err)
		return err
	}

	// write source code into file.

	srcPath := filepath.Join(submissionDir, conf.SrcName)
	var srcFile *os.File
	if srcFile, err = os.Create(srcPath); err != nil {
		global.Logger.Errorf("create source file err:%v", err)
	}
	defer srcFile.Close()

	_, err = srcFile.WriteString(input.Src)

	if err != nil {

		global.Logger.Errorf("write source file err:%v", err)
		return err
	}

	// compile the source code

	var exePath string

	exePath, err = compiler.Compile(conf.CompileConfig, srcPath, submissionDir)

	if err != nil {
		global.Logger.Errorf("compile err:%v", err)
		return err
	}

	// parse the run command
	// todo: parse the java's exe_dir and max_memory limit

	conf.RunConfig.Command.FillWith(map[string]string{
		"{exe_path}":   exePath,
		"{exe_dir}":    submissionDir,
		"{max_memory}": strconv.Itoa(input.MaxMemory / 1024),
	})

	// run the client

	js := service.JudgeService{
		Sid:           input.Sid,
		MaxCpuTime:    input.MaxCPUTime,
		MaxMemory:     input.MaxMemory,
		ExePath:       exePath,
		ProblemId:     input.TestCaseID,
		SubmissionDir: submissionDir,
		RunConf:       conf.RunConfig,
	}

	result, err := js.Judge()
	byteData, _ := json.Marshal(result)
	//req, _ := http.NewRequest("POST", global.Conf.Server.OjCenterAddr, bytes.NewReader(byteData))
	//req.Header.Add("Content-Type", "application/json")
	//resp, err := client.JClient.HttpCli.Do(req)
	defer func() {
		err = c.P.Publish(global.Conf.Server.NsqProTopic, byteData)
		if err != nil {
			global.Logger.Errorf("send run result err:%v", err)
		}
		//data, _ := io.ReadAll(resp.Body)
		//resp.Body = io.NopCloser(bytes.NewBuffer(data))
		message.Finish()
	}()

	return nil
}

func initSubmissionEnv(submissionID string) (string, error) {
	submissionDirPath := filepath.Join(global.Conf.Path.WorkDir, global.Conf.Path.SubmissionDir, submissionID)

	if err := os.Mkdir(submissionDirPath, 0777); err != nil {
		return "", err
	}

	return submissionDirPath, nil
}

func freeSubmissionEnv(submissionDirPath string) error {
	return os.RemoveAll(submissionDirPath)
}
